package org.codehaus.groovy.grails.plugins.acegi;

import groovy.lang.GroovyObject;

import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.grails.commons.GrailsDomainClass;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.hibernate.collection.PersistentSet;
import org.springframework.dao.DataAccessException;

public class DomainObjectUserDetailsService extends
        GrailsWebApplicationObjectSupport implements UserDetailsService
{

    private static final Log logger = LogFactory
            .getLog(DomainObjectUserDetailsService.class);

    private String mDomainClass;
    private String mUsernameField;
    private String mPasswordHashField;
    private String mEnabledField;
    private String mRolesField;
    private String mRoleNameField;

    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException, DataAccessException
    {
        UserDetailsWrapper wrapper = null;

        setUpSession();

        GrailsDomainClass domainClass = (GrailsDomainClass)getGrailsApplication()
                .getArtefact("Domain", mDomainClass);

        GroovyObject userDetails = (GroovyObject)InvokerHelper
                .invokeStaticMethod(domainClass.getClazz(), toFindBy(mUsernameField), username);

        if(userDetails != null)
        {
            logger.debug("User found: " + userDetails);

            String passwordHash = (String)InvokerHelper.invokeMethod(
                    userDetails, toGetter(mPasswordHashField), null);
            Boolean enabled = (Boolean)InvokerHelper.invokeMethod(userDetails,
                    toGetter(mEnabledField), null);

            PersistentSet roles = (PersistentSet)InvokerHelper.invokeMethod(
                    userDetails, toGetter(mRolesField), null);
            if(roles == null || roles.size() == 0)
            {
                logger.error("User [" + username + "] has no GrantedAuthority");
                throw new UsernameNotFoundException("User has no roles");
            }
            GrantedAuthority[] authorities = new GrantedAuthorityImpl[roles
                    .size()];
            int num = 0;
            for(Object role : roles)
            {
                String authority = (String)InvokerHelper.invokeMethod(
                        (GroovyObject)role, toGetter(mRoleNameField), null);
                authorities[num] = new GrantedAuthorityImpl(authority);
                num++;
            }

            wrapper = new UserDetailsWrapper(username, passwordHash, enabled
                    .booleanValue(), true, true, true, authorities);
            wrapper.setDomainClass(userDetails);
        }
        else
        {
            logger.error("User not found: " + username);
            releaseSession();
            throw new UsernameNotFoundException("User not found.", username);
        }

        releaseSession();
        return wrapper;
    }

    public String getDomainClass()
    {
        return mDomainClass;
    }

    public void setDomainClass(String domainClass)
    {
        mDomainClass = domainClass;
    }

    public String getUsernameField()
    {
        return mUsernameField;
    }

    public void setUsernameField(String usernameField)
    {
        mUsernameField = usernameField;
    }

    public String getPasswordHashField()
    {
        return mPasswordHashField;
    }

    public void setPasswordHashField(String passwordHashField)
    {
        mPasswordHashField = passwordHashField;
    }

    public String getEnabledField()
    {
        return mEnabledField;
    }

    public void setEnabledField(String enabledField)
    {
        mEnabledField = enabledField;
    }

    public String getRolesField()
    {
        return mRolesField;
    }

    public void setRolesField(String rolesField)
    {
        mRolesField = rolesField;
    }

    public String getRoleNameField()
    {
        return mRoleNameField;
    }

    public void setRoleNameField(String roleNameField)
    {
        mRoleNameField = roleNameField;
    }

    private String toFindBy(String field)
    {
        return "findBy" + upcase(field);
    }

    private String toGetter(String field)
    {
        return "get" + upcase(field);
    }

    private String upcase(String field)
    {
        return field.substring(0, 0).toUpperCase() + field.substring(1);
    }
}
